#include "iConfig.hpp"

#import <glf/iphone/AppDelegate.h>

#include <glf/app.h>
#include <glf/core/thread.h>
#include <glf/debug/console.h>
#include <glf/debug/fileLogger.h>
#include <glf/input/inputEvent.h>

#include <glf/core/infos.h>
#include <sstream>

#include <glf/iphone/Reachability.h>

#include <sys/sysctl.h>

#include <libkern/OSMemoryNotification.h>

#ifndef GLF_USE_CRASHREPORTER
#define GLF_USE_CRASHREPORTER 0
#endif

#ifndef GLF_USE_LAUNCHREPORTER
#define GLF_USE_LAUNCHREPORTER 0
#endif

#if GLF_USE_CRASHREPORTER
#import "CrashReporter.h"
#endif

const char* gPropSessionWifiChange = "session.wifiChanges";

#if _DEBUG
#define PRINT_EVT printf
#else
#define PRINT_EVT
#endif

#include <inttypes.h>
#import <SystemConfiguration/CaptiveNetwork.h>

enum DebugLogInfo {
	EDLI_NONE					= 0x00,
	EDLI_CRASHLOGS				= 0x01,
	EDLI_DEBUGLOGS				= 0x02,
	EDLI_FULLMAP				= 0x04,
	EDLI_SUSPENDRESUME			= 0x08,
	EDLI_FORCELOGGING			= 0x10,
	EDLI_ALL					= 0xff,
};

enum MessageType {
	EMT_FIRSTCONNEXION,
	EMT_SUSPENDRESUME,
	EMT_SENDLOG,
};

struct ThreadData {
	int debugLogInfo;
	MessageType mt;
	glf::PropertyMap::map_t properties;
	std::string log;
	std::string oldLog;

	ThreadData(MessageType mt)
		:	debugLogInfo(EDLI_NONE)
		,	mt(mt)
	{
		properties = glf::PropertyMap::Instance().GetProperties();
		glf::FileLogger::GetInstance()->GetCurrentLog(log);
		glf::FileLogger::GetInstance()->GetLastLog(oldLog);
	}

};

std::string glf::GetSSID() {
	std::string wifiSSID;
	CFArrayRef interfaces = CNCopySupportedInterfaces();
	if(interfaces) {
		CFIndex count = CFArrayGetCount(interfaces);
		for(int i = 0; i < count; ++i) {
			CFStringRef interface = (CFStringRef)CFArrayGetValueAtIndex(interfaces, i);
			if(CFStringCompare(interface, CFSTR("en0"), 0) == kCFCompareEqualTo) {
				CFDictionaryRef netinfo = CNCopyCurrentNetworkInfo(interface);
				if(netinfo) {
					if(CFDictionaryContainsKey(netinfo, kCNNetworkInfoKeySSID)) {
						NSString* ssid = (NSString*)CFDictionaryGetValue(netinfo, kCNNetworkInfoKeySSID);
						wifiSSID = [ssid UTF8String];
					}
					CFRelease(netinfo);
				}
			}
		}
	}
	CFRelease(interfaces);
	
	return wifiSSID;
}

/*
[isActive]
[  lockPhone] -> applicationWillResignActive
[unlockPhone] -> applicationDidBecomeActive
[double home tap] -> applicationWillResignActive & applicationDidEnterBackground
	[out of memory] -> applicationDidReceiveMemoryWarning
	[resume] -> applicationDidEnterForeground & applicationDidBecomeActive
	[killapp]
		[isMultitask] -> 
		[!isMultitask] -> applicationWillTerminate
*/

/*
 to set the build.* properties
 add a build phase
 cd <location to glf>/projects/xcode
 chmod +x writeBuildIdToPlist.sh
 ./writeBuildIdToPlist.sh
 
 only quirk with this: use the default Info.plist name. it might not work otherwise
 */

AppDelegate* gAppDelegate = Nil;
glf::App* gGameApp = 0;

// UpdateThread
class UpdateThread : public glf::Thread
{
public:
	static const int STACK_SIZE = 64 * 1024;
	
public:
	UpdateThread() : glf::Thread(STACK_SIZE)
	{
	}
	virtual void Run()
	{
		if (gGameApp) {		
			gGameApp->SetAsMainThread();
			gGameApp->Prepare(0);
			while (gGameApp->IsRunning())
			{
				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
				gGameApp->Update();
				[pool drain];
			}			
		}
	}
};

bool IsRetina() 
{ 
	UIScreen* screen = [UIScreen mainScreen]; 
	
	if ([screen respondsToSelector:@selector(currentMode)] == NO) 
		return false; 

	CGSize screenSize = screen.currentMode.size; 
	
	float h = (float)screenSize.height; 
	
	float w = (float)screenSize.width; 
	
	if (w > h) 
		return (w >= 960 && w/h == 1.5f) || w > 1024; 
	else 
		return (h >= 960 && h/w == 1.5f) || h > 1024; 
}

@interface AppDelegate()

- (void) addScreen:(id)screenId;
- (void) cocoaThread: (id)arg;
- (void) createGLContexts:(glf::PlatformInfo&)pi;
- (void) createMainWindow;
- (void) createSecondWindow:(UIScreen*)screen;
- (GenericGLView *) createViewForScreen:(UIScreen *)screen;
- (void) didRotate:(NSNotification *)notification;
- (void) handleScreenConnectNotification:(NSNotification*)aNotification;
- (void) handleScreenDisconnectNotification:(NSNotification*)aNotification;
- (void) removeScreen:(id)screenId;
- (void) startAnimation;
- (void) stopAnimation;
- (void) sendLog;

@end

@implementation AppDelegate

@synthesize context;

@synthesize mMainWindow, mSecondWindow;
@synthesize mMainView, mSecondView;
@synthesize mTextField;

@synthesize animating;
@dynamic animationFrameInterval;
@synthesize animationInterval;
@synthesize animationTimer;

@synthesize isForeground;
@synthesize useEventPump;

+ (id) alloc
{
	AppDelegate *newid = [super alloc];
	
	newid->animating = FALSE;
	newid->mUpdateMode = glf::UPDATE_MODE_NONE;
	newid->mIsMultipleScreenSupported = false;
	newid->mUpdateThread = nil;
	newid->displayLink = nil;
	newid->animationTimer = nil;
	newid->animationInterval = (1.0 / 60.0);
	newid->isForeground = FALSE;
	newid->useEventPump = TRUE;
	
	return newid;
}

+ (void)InitView:(glf::CreationSettings&)cs infos:(glf::PlatformInfo&)pi 
{
	[gAppDelegate InitView:cs infos:pi];
}

+ (BOOL) Prepare:(int)windowIndex
{
	return [gAppDelegate Prepare:windowIndex];
}

+ (void) Present:(int)windowIndex
{
	[gAppDelegate Present:windowIndex];
}

+ (EAGLContext *) GetMainContext 
{
	return [gAppDelegate context];
}

+ (int) GetFrameBufferAtScreenIndex:(int)screenIndex
{
	return [gAppDelegate GetFrameBufferAtScreenIndex:screenIndex];
}

+ (void) GetWindowSizeAtScreenIndex:(int)screenIndex 
							  Width:(int &)width 
							 Height:(int &)height
{
	return [gAppDelegate GetWindowSizeAtScreenIndex:screenIndex Width:width Height:height];
}

+ (BOOL) ConnectDisplay:(int)windowIndex
{
	return [gAppDelegate ConnectDisplay:windowIndex];
}

+ (BOOL) DisconnectDisplay:(int)windowIndex
{
	return [gAppDelegate DisconnectDisplay:windowIndex];
}

+ (glf::DisplayStatus) GetDisplayStatus:(int)windowIndex
{
	return [gAppDelegate GetDisplayStatus:windowIndex];
}

+ (void) UpdateKeyboard:(BOOL)visible DefaultText:(const std::wstring&)defaultText DefaultTextID:(int)defaultTextID
{
	[gAppDelegate UpdateKeyboard:visible DefaultText:defaultText DefaultTextID:defaultTextID];
}

- (void)dealloc 
{
	[self stopAnimation];

	[mMainView release];
	[mMainWindow release];
	[context release];
	[super dealloc];
}

- (GenericGLView*) NewGLView:(BOOL)isMainScreen 
{
	if (isMainScreen)
		return [MainGLView alloc];
	return [GenericGLView alloc];
}

- (void)InitView:(glf::CreationSettings&)cs infos:(glf::PlatformInfo&)pi 
{	
	UIScreen* mainScreen = [UIScreen mainScreen];
	CGRect screenBounds = [mainScreen bounds];
	
	// Get main window dimensions
	float scale = 1.0f;
	if([mainScreen respondsToSelector:@selector(scale)]) 
	{
		if(IsRetina())
			scale = [mainScreen scale];
	}
	
	cs.screenScale	= glf::Max(scale, 1.0f);
	cs.windowWidth	= screenBounds.size.width * cs.screenScale;
	cs.windowHeight = screenBounds.size.height * cs.screenScale;	
	
	//the following code seems to cause a bug on ipad3/sdk 5.1  (displays only on the bottom left corner of the screen)
	// Adjust window dimensions: convert points to pixels according to scale
	//if([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)])
	//{
	//	int idiom = [[UIDevice currentDevice] userInterfaceIdiom];
	//	if (idiom == UIUserInterfaceIdiomPad)
	//	{
	//		cs.windowWidth /= cs.screenScale;
	//		cs.windowHeight /= cs.screenScale;
	//		cs.screenScale = 1.0f;
	//	}
	//}

	// Create GL context
	if((cs.driverType & glf::OPENGL_ES) == 0) {
		cs.driverType = glf::OPENGL_ES;
	}	
	mCreationSettings = new glf::CreationSettings(cs);
	[self createGLContexts:pi];			
		
	mUpdateMode = cs.updateMode;
	
	UIDevice* device = [UIDevice currentDevice];
	
	// Check system version
	NSString *currSysVer = [device systemVersion];
	
	// Check Display Link capacity if necessary
	// A system version of 3.1 or greater is required to use 
	// CADisplayLink. The NSTimer class is used as fallback 
	// when it isn't available.		
	if (mUpdateMode == glf::UPDATE_MODE_DISPLAY_SYNC) {		
		if ([currSysVer compare:@"3.1" options:NSNumericSearch] == NSOrderedAscending)
			// Use timer if display link is not supported
			mUpdateMode = glf::UPDATE_MODE_TIMER;
	}	
	// Check multiple screen capacity: a system version of 3.2 or greater is required.
	mIsMultipleScreenSupported = 
		[currSysVer compare:@"3.2" options:NSNumericSearch] != NSOrderedAscending;
	NSLog(@"Multiple screen supported: %d\n", mIsMultipleScreenSupported);
	
	// Create main window
	[self createMainWindow];
	
	// Init event handling
	[mMainWindow makeKeyAndVisible];	
	[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
	NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
	[center addObserver:self selector:@selector(didRotate:) name:@"UIDeviceOrientationDidChangeNotification" object:nil];	
	if (mIsMultipleScreenSupported)
	{
		[center addObserver:self selector:@selector(handleScreenConnectNotification:) name:UIScreenDidConnectNotification object:nil];
		[center addObserver:self selector:@selector(handleScreenDisconnectNotification:) name:UIScreenDidDisconnectNotification object:nil];
	}
	
	// Go
	[self startAnimation];
}


- (void) getThreadSnapshot:(std::string&)callstack
{
#if GLF_USE_CRASHREPORTER
	PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
	
	if(crashReporter)
	{
		NSError* error;
		if([crashReporter generateLiveReportAndReturnError: &error])
		{
			NSData* crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error];
			
			PLCrashReport *report = [[[PLCrashReport alloc] initWithData: crashData error: &error] autorelease];			
			NSString* iosCrashLog = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS];		
			
			callstack = [iosCrashLog UTF8String];
			
			[crashReporter purgePendingCrashReport];
		}
	}
#endif
}


#if GLF_USE_LAUNCHREPORTER

- (NSString*)urlEncodeValue:(NSString*)str
{
	NSString* result = (NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)str, NULL, CFSTR("?=&+"), kCFStringEncodingUTF8);
	return [result autorelease];
}

- (NSData*) buildHttpRequestDataWithData:(ThreadData&)data
						    AddDictFlag:(int)addDictFlag 
						         AddLog:(bool)addLog 
						     AddPrevLog:(bool)addPrevLog 
							AddCrashLog:(bool)addCrashLog 
							   AddNsLog:(bool)addNsLogs
{
	NSMutableDictionary* postDict = [[[NSMutableDictionary alloc] init] autorelease];
	
	#if GLF_USE_CRASHREPORTER
	PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
	if(addCrashLog) {
		if([crashReporter hasPendingCrashReport]) {
			addPrevLog = true;
			NSError *error;
			NSData* crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error];
			PLCrashReport *report = [[[PLCrashReport alloc] initWithData: crashData error: &error] autorelease];
			
			glf::PropertyMap::Value value([[report.systemInfo.timestamp description] UTF8String], glf::PropertyMap::EF_LR_QUERY_FIRST|glf::PropertyMap::EF_LR_QUERY_FULL);
			data.properties["crash.time"] = value;
			
			NSString* iosCrashLog = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS];
			[postDict setValue:iosCrashLog forKey:@"crashlog"];
		} else {
			//[postDict setValue:@"no pending crashlog" forKey:@"crashlog"];
		}
	}
	#endif
	
	for(glf::PropertyMap::map_t::const_iterator it = data.properties.begin(); it != data.properties.end(); ++it) {
		const glf::PropertyMap::key_t& key = it->first;
		const glf::PropertyMap::value_t& value = it->second;
		
		int flag = value.flags;
		std::string valueAsString = value.getAsString();
		
		if(flag & addDictFlag)
		{
			NSString* nsKey		= [NSString stringWithUTF8String:key.c_str()];
			NSString* nsValue	= [NSString stringWithUTF8String:valueAsString.c_str()];
		
			[postDict setValue:nsValue forKey:nsKey];
		}
/*		
		if(addFullDict) 
		{
			NSString* nsKey		= [NSString stringWithUTF8String:key.c_str()];
			NSString* nsValue	= [NSString stringWithUTF8String:valueAsString.c_str()];
			
			[postDict setValue:nsValue forKey:nsKey];
		}
		else
		{
			if(		key.find("session.launchUid") == 0
				||	key.find("session.lastLaunchCrashed") == 0
				||	key.find("build.uuid") == 0
				||	key.find("last.session.wifiChanges") == 0)
			{
				NSString* nsKey		= [NSString stringWithUTF8String:key.c_str()];
				NSString* nsValue	= [NSString stringWithUTF8String:valueAsString.c_str()];
				
				[postDict setValue:nsValue forKey:nsKey];
			}
		}
*/		
	}	
	
	if(addLog) {
		NSString* nsLog = [NSString stringWithUTF8String:data.log.c_str()];
		[postDict setValue:nsLog forKey:@"logs"];
	}
	
	if(addPrevLog) {
		NSString* nsLog = [NSString stringWithUTF8String:data.oldLog.c_str()];
		[postDict setValue:nsLog forKey:@"logs"];
	}
	
	//if(addNsLogs) {
	//	[postDict setValue:@"baba2" forKey:@"nslogs"];
	//}
	
	NSString* argumentList = [[NSString new] autorelease];
	for(NSString* key in postDict) {
		NSString* rawValue = [postDict valueForKey:key];
		NSString* encodedValue = [self urlEncodeValue:rawValue];
		NSString* pair;

		if([argumentList length] == 0) {
			pair = @"%@=%@";
			argumentList = [NSString stringWithFormat:pair, key, encodedValue];

		} else {
			pair = @"%@&%@=%@";
			argumentList = [NSString stringWithFormat:pair, argumentList, key, encodedValue];
		}
	}
	
	NSData* requestData = [NSData dataWithBytes:[argumentList UTF8String] length:[argumentList length]];
	
	return requestData;
}

- (void) sendLog
{
	ThreadData* data = new ThreadData(EMT_SENDLOG);
	
	[NSThread detachNewThreadSelector:@selector(handleReportJob:)
							 toTarget:self 
						   withObject:[NSValue valueWithPointer:data]];
}

- (void)sendRequestData:(NSData*)requestData
			Response:(int&)intResponse
{
	NSString* nsUrlString = @"http://astor-alpha.gameloft.com/report.php";
	
	NSMutableURLRequest* urlRequest = [[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:nsUrlString]] autorelease];
	[urlRequest setHTTPMethod:@"POST"];
	
	[urlRequest setValue:@"application/x-www-form-urlencoded"
	  forHTTPHeaderField:@"content-type"];
	[urlRequest setHTTPBody:requestData];
	
	NSError* error = nil;
	NSURLResponse* response = nil;
	
	[NSURLConnection sendSynchronousRequest:urlRequest 
						  returningResponse:&response 
									  error:&error];
	
	intResponse = [(NSHTTPURLResponse*)response statusCode];
}

- (void)handleReportJob:(NSValue *) val
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	ThreadData* data = static_cast<ThreadData*>([val pointerValue]);
	
	glf::PropertyMap::key_t reportTypeKey = "report_type";
	
	if(data->mt == EMT_FIRSTCONNEXION) 
	{
		{
			glf::PropertyMap::Value value("first", glf::PropertyMap::EF_LR_ALWAYS);
			data->properties[reportTypeKey] = value;
		}
		
		NSData* requestData = [self buildHttpRequestDataWithData:*data
											     AddDictFlag:glf::PropertyMap::EF_LR_QUERY_FIRST
													  AddLog:false 
												  AddPrevLog:false 
												 AddCrashLog:false 
													AddNsLog:false];

		
		{
			glf::PropertyMap::Value value("full", glf::PropertyMap::EF_LR_ALWAYS);
			data->properties[reportTypeKey] = value;
		}
		
		const int SUCCESS_CODE = 200;
		int response;
		[self sendRequestData:requestData Response:response];
		if(response == SUCCESS_CODE)
		{
			mLaunchReporterResponse = EDLI_ALL;
		}
		else if(response - SUCCESS_CODE > 0) {
			response -= SUCCESS_CODE;
			if(response & EDLI_CRASHLOGS)		mLaunchReporterResponse |= EDLI_CRASHLOGS;
			if(response & EDLI_DEBUGLOGS)		mLaunchReporterResponse |= EDLI_DEBUGLOGS;
			if(response & EDLI_FULLMAP)			mLaunchReporterResponse |= EDLI_FULLMAP;
			if(response & EDLI_SUSPENDRESUME)	mLaunchReporterResponse |= EDLI_SUSPENDRESUME;
			if(response & EDLI_FORCELOGGING)	mLaunchReporterResponse |= EDLI_FORCELOGGING;
		}
	
		bool sendCrashLog		= (mLaunchReporterResponse & EDLI_CRASHLOGS)!= 0;
		bool sendDebugLog		= (mLaunchReporterResponse & EDLI_DEBUGLOGS)!= 0;
		bool sendFullMap		= (mLaunchReporterResponse & EDLI_FULLMAP)!= 0;
		bool sendSuspendResume	= (mLaunchReporterResponse & EDLI_SUSPENDRESUME)!= 0;
		bool forceLogging		= (mLaunchReporterResponse & EDLI_FORCELOGGING) != 0;
	
		glf::FileLogger::GetInstance()->Enable(forceLogging);
	
		if(sendCrashLog || sendDebugLog || sendFullMap) {
			requestData = [self buildHttpRequestDataWithData:*data
										     AddDictFlag:glf::PropertyMap::EF_LR_QUERY_FULL 
											      AddLog:false
											  AddPrevLog:sendDebugLog 
											 AddCrashLog:sendCrashLog 
												AddNsLog:true];
		
			[self sendRequestData:requestData Response:response];
		}
		
#if GLF_USE_CRASHREPORTER
		PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
		[crashReporter purgePendingCrashReport];		
#endif
	} 
	else if(data->mt == EMT_SENDLOG)
	{
		glf::PropertyMap::Value value("sendLog", glf::PropertyMap::EF_LR_ALWAYS);
		data->properties[reportTypeKey] = value;
		
		NSData* requestData = [self buildHttpRequestDataWithData:*data
													 AddDictFlag:glf::PropertyMap::EF_NONE
														  AddLog:true 
													  AddPrevLog:false 
													 AddCrashLog:false 
														AddNsLog:false];
		int response;
		[self sendRequestData:requestData Response:response];
	}
	else if(data->mt == EMT_SUSPENDRESUME)
	{
		glf::PropertyMap::Value value("onResume", glf::PropertyMap::EF_LR_ALWAYS);
		data->properties[reportTypeKey] = value;
		
		NSData* requestData = [self buildHttpRequestDataWithData:*data
													 AddDictFlag:glf::PropertyMap::EF_LR_QUERY_SUSPENDRESUME
														  AddLog:false 
													  AddPrevLog:false 
													 AddCrashLog:false 
														AddNsLog:false];
		int response;
		[self sendRequestData:requestData Response:response];
	}
	
	delete data;
		
	[pool drain];
}

#else
- (void) sendLog
{
	
}

#endif

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *) launchOptions 
{
	gAppDelegate = self;

#if GLF_USE_CRASHREPORTER

	PLCrashReporter* crashReporter = [PLCrashReporter sharedReporter];
	
	NSError* error;
	if(![crashReporter enableCrashReporterAndReturnError: &error])
		NSLog(@"warning: could not enable crash reporter: %@", error);
	
#endif
	return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application 
{
	isForeground = FALSE;
	GetAppEventMgr().SendEvent(glf::SystemEvent(glf::SET_IPHONE_ENTERBACKGROUND));
}

- (void)applicationWillEnterForeground:(UIApplication *)application 
{
	isForeground = TRUE;
	mLastSSID = glf::GetSSID();
	GetAppEventMgr().SendEvent(glf::SystemEvent(glf::SET_IPHONE_ENTERFOREGROUND));
}

- (void)applicationWillTerminate:(UIApplication *)application 
{
	isForeground = FALSE;
	[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
	[[NSNotificationCenter defaultCenter] addObserver:nil selector:nil name:@"UIDeviceOrientationDidChangeNotification" object:nil];
	GetAppEventMgr().SendEvent(glf::SystemEvent(glf::SET_IPHONE_WILL_TERMINATE));
}

- (void)applicationWillResignActive:(UIApplication *)application 
{
	// Disconnect external display
	[self DisconnectDisplay:1];
	isForeground = FALSE;
	GetAppEventMgr().SendEvent(glf::SystemEvent(glf::SET_IPHONE_WILL_RESIGN_ACTIVE));
	
#if GLF_USE_LAUNCHREPORTER
	
	//it seems impossible to garantee that the application will have enough time to send the http request
	//before the os puts us in suspended mode.
	//so dont do anything in this case
	/*
	if(mLaunchReporterResponse & EDLI_SUSPENDRESUME)
	{
		ThreadData* data = new ThreadData(EMT_SUSPENDRESUME);
		
		NSThread* thread = 
		[[NSThread alloc] initWithTarget:self
								selector:@selector(handleReportJob:)
						  object:[NSValue valueWithPointer:data]];
		//[NSThread detachNewThreadSelector:@selector(handleReportJob:)
		//   						toTarget:self 
		//	  				withObject:[NSValue valueWithPointer:data]];
		
		UIBackgroundTaskIdentifier bti = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
		
		[thread start];
		
		if(bti != UIBackgroundTaskInvalid) {
		
			while(![thread isFinished]) {
				usleep(2*1000);
			}
			[[UIApplication sharedApplication] endBackgroundTask:bti];
		}
	}
	*/
#endif
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application 
{
	int level = OSMemoryNotificationCurrentLevel();
	
	glf::PropertyMap& pm = glf::PropertyMap::Instance();
	
	const char* key = "session.memoryWarningLevel";
	
	int oldLevel = pm.GetPropertyInt32(key, level);
	
	pm.SetPropertyT<int>(key, std::max(level, oldLevel), glf::PropertyMap::EF_PERSISTANT|glf::PropertyMap::EF_LR_QUERY_FULL);
	
	GetAppEventMgr().SendEvent(glf::SystemEvent(glf::SET_IPHONE_LOW_MEMORY));
}

- (void)applicationDidBecomeActive:(UIApplication *)application 
{
	isForeground = TRUE;
	bool firstTime = gGameApp == 0;
	if(gGameApp == 0) {
		gGameApp = NewApp(glf::GetGlobals()->argc, glf::GetGlobals()->argv);
		gGameApp->MyInit();
		
#if GLF_USE_LAUNCHREPORTER
#if GLF_USE_CRASHREPORTER	
	PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
	bool lastLaunchCrashed = [crashReporter hasPendingCrashReport];
	
	glf::PropertyMap& pm = glf::PropertyMap::Instance();
	
	pm.SetPropertyT<int>(
		"session.lastLaunchCrashed", 
		lastLaunchCrashed?1:0, 
		glf::PropertyMap::EF_PERSISTANT|glf::PropertyMap::EF_LR_QUERY_FIRST|glf::PropertyMap::EF_LR_QUERY_FULL);

#endif

		glf::Console::Println("test glf::Console::Println");
		
		ThreadData* data = new ThreadData(EMT_FIRSTCONNEXION);
		
		[NSThread detachNewThreadSelector:@selector(handleReportJob:)
								 toTarget:self 
							   withObject:[NSValue valueWithPointer:data]];

#endif
		
	}		
	
	// Connect external display
	if ([self GetDisplayStatus:1] == glf::DS_AVAILABLE)
		[self addScreen:[[UIScreen screens] objectAtIndex:1]];
	GetAppEventMgr().SendEvent(glf::SystemEvent(glf::SET_IPHONE_BECAME_ACTIVE));
	
#if GLF_USE_LAUNCHREPORTER
	if(!firstTime && (mLaunchReporterResponse & EDLI_SUSPENDRESUME))
	{
		ThreadData* data = new ThreadData(EMT_SUSPENDRESUME);
			
		[NSThread detachNewThreadSelector:@selector(handleReportJob:)
								 toTarget:self 
							   withObject:[NSValue valueWithPointer:data]];	
	}
#endif
}

- (void) didRotate:(NSNotification *)notification 
{
	UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
	glf::OrientationEventType glfEventMapping[] = {
		(glf::OrientationEventType)-1,
		glf::OET_ASPECTPORTRAIT, 
		glf::OET_ASPECTPORTRAIT_REVERSED,
		glf::OET_ASPECTLANDSCAPE_RIGHT,
		glf::OET_ASPECTLANDSCAPE_LEFT,
		glf::OET_ASPECTFACE_UP,
		glf::OET_ASPECTFACE_DOWN,
	};	
	GetAppEventMgr().SendEvent(glf::OrientationEvent(glfEventMapping[orientation]));
}

- (void) createGLContexts:(glf::PlatformInfo&)pi 
{
	// Try GLES2 then GLES1
	context = Nil;
	if (mCreationSettings->driverType & glf::OPENGL_ES) {
		
		//GLES2
		if(mCreationSettings->driverType & glf::OPENGL_ES_2) {
			context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];			
		}
		
		//GLES1
		if(!context && mCreationSettings->driverType & glf::OPENGL_ES_1){
			context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
		}

		// Add extra contexts based on current context API
		if(context) {
			for(int i = 0; i < mCreationSettings->extraContexts; ++i) {
				EAGLContext* ctx = [[EAGLContext alloc] initWithAPI:context.API sharegroup:context.sharegroup];
				pi.mExtraContext[i] = (void*)ctx;
			}
		}		
		
		if (!context || ![EAGLContext setCurrentContext:(EAGLContext*)context]) {
			glf::Console::Print("Error while creating OpenGL context....");
			return;
		}			
		
	}
}

- (NSInteger) animationFrameInterval
{
	return animationFrameInterval;
}

- (void) setAnimationFrameInterval:(NSInteger)frameInterval
{
	// Frame interval defines how many display frames must pass 
	// between each time the display link fires. The display link 
	// will only fire 30 times a second when the frame internal 
	// is two on a display that refreshes 60 times a second. The 
	// default frame interval setting of one will fire 60 times a 
	// second when the display refreshes at 60 times a second. A 
	// frame interval setting of less than one results in undefined
	// behavior.
	if (frameInterval >= 1)
	{
		animationFrameInterval = frameInterval;
		
		if (animating)
		{
			[self stopAnimation];
			[self startAnimation];
		}
	}
}

- (void) cocoaThread: (id)arg
{
	NSLog(@"cocoaThread");
}

- (void) startAnimation
{
	if (!animating)
	{
		switch (mUpdateMode)
		{
		case glf::UPDATE_MODE_DISPLAY_SYNC:
			{	
				displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(drawView:)];
				[displayLink setFrameInterval:animationFrameInterval];
				[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
			}
			break;
		case glf::UPDATE_MODE_TIMER:
			{
				animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval 
					target:self 
					selector:@selector(drawView:) 
					userInfo:nil 
					repeats:YES];
			}		
			break;
		case glf::UPDATE_MODE_THREAD:	
			{
				if (!mUpdateThread) {
					if (![NSThread isMultiThreaded]) {
						// need to create a fake cocoa thread to setup multi-threaded mode
						// http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
						[NSThread detachNewThreadSelector:@selector(cocoaThread:) toTarget:self withObject:nil];
						GLF_ASSERT([NSThread isMultiThreaded]);
						NSLog(@"Multithreaded mode is now enabled");	
					}
					else {
						NSLog(@"Multithreaded mode is already enabled");	
					}					
					mUpdateThread = new UpdateThread();
					mUpdateThread->Start();
				}
			}		
			break;
		case glf::UPDATE_MODE_EVENTPUMP:
			{
				animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval 
					target:self 
					selector:@selector(drawViewEventPump:) 
					userInfo:nil 
					repeats:NO];
			}		
			break;
		default:
			// Do not set animating to TRUE
			return;
		}
		animating = TRUE;
	}
}

- (void)stopAnimation
{
	if (animating)
	{
		switch (mUpdateMode)
		{
		case glf::UPDATE_MODE_DISPLAY_SYNC:
			{
				[displayLink invalidate];
				displayLink = nil;
			}
			break;
		case glf::UPDATE_MODE_TIMER:
			{
				[animationTimer invalidate];
				animationTimer = nil;
			}		
			break;
		case glf::UPDATE_MODE_THREAD:
			{
				if (mUpdateThread) {
					if (gGameApp)
						gGameApp->Stop();
					delete mUpdateThread;
					mUpdateThread = nil;
				}
			}		
			break;
		case glf::UPDATE_MODE_EVENTPUMP:
			{
				[animationTimer invalidate];
				animationTimer = nil;
			}	
			break;
		}
		
		animating = FALSE;
	}
}

- (void) drawView:(id)sender 
{
	if (gGameApp) {
		gGameApp->Update();
	}
	
	/*static int frame = 0;
	if(frame++ == 500) {
		int* array = 0;
		array[0] = 0;
	}
	 */
}

- (void) PumpEvents:(int)sleepTimeUS AndDraw:(BOOL)draw
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource);
	if (draw)
	{
		[self drawView:nil];
	}
	[pool drain];
	usleep(sleepTimeUS);
}

- (void) drawViewEventPump:(id)sender
{
	if (isForeground && useEventPump)
	{
		NSLog(@"Start pump events");
		// Quasi infinite loop: pump events, draw and sleep a bit
		while (isForeground && useEventPump)
		{
			[self PumpEvents:1000 AndDraw:true];
		}
		NSLog(@"Stop pump events");
	}
	else if (isForeground)
	{
		// Single call to drawView()
		// drawViewEventPump() will be recalled below when restarting animation 
		[self drawView:nil];
	}
	else
	{
		// Single call to App::Update()
		// drawViewEventPump() will be recalled...
		if (gGameApp)
		{
			gGameApp->Update();
		}
	}
	// Restart animation and indirectly recalls drawViewEventPump()
	if (animating)
	{
		[self stopAnimation];
		[self startAnimation];
	}
}

- (BOOL) Prepare:(int)windowIndex
{
	if (windowIndex == 0)
	{
		[mMainView bind];
		return TRUE;
	}
	if (windowIndex == 1 && mSecondView)
	{
		[mSecondView bind];
		return TRUE;		
	}
	return FALSE;
}

- (void) Present:(int)windowIndex
{
	if (windowIndex == 0)
	{
		[mMainView flush];
		return;
	}
	if (windowIndex == 1 && mSecondView)
	{
		[mSecondView flush];
		return;		
	}
	GLF_ASSERT(!"Not valid window index");
}

- (BOOL) ConnectDisplay:(int)windowIndex
{
	if (windowIndex == 1 && [self GetDisplayStatus:windowIndex] == glf::DS_AVAILABLE) {			
		UIScreen *screen = [[UIScreen screens] objectAtIndex:1];
		// Execute the window/view creation in the main thread
		[self performSelectorOnMainThread:@selector(addScreen:) withObject:screen waitUntilDone:FALSE];
		return TRUE;
	}
	return FALSE;
}

- (BOOL) DisconnectDisplay:(int)windowIndex
{
	if (windowIndex == 1 && [self GetDisplayStatus:windowIndex] == glf::DS_CONNECTED) {
		UIScreen *screen = [[UIScreen screens] objectAtIndex:1];		
		// Execute the window/view destruction in the main thread
		[self performSelectorOnMainThread:@selector(removeScreen:) withObject:screen waitUntilDone:FALSE];
		return TRUE;
	}
	return FALSE;
}

- (glf::DisplayStatus) GetDisplayStatus:(int)windowIndex
{
	switch(windowIndex)
	{
	case 0:
		return glf::DS_CONNECTED;
	case 1:
		if (mSecondWindow)
			return glf::DS_CONNECTED;
		if (mIsMultipleScreenSupported)
		{
			if ([[UIScreen screens] count] > 1)
				return glf::DS_AVAILABLE;
		}
		break;
	}
	return glf::DS_UNAVAILABLE;
}

//Called by Reachability whenever status changes.
- (void) reachabilityChanged: (NSNotification* )note
{
    Reachability* curReach = [note object];
    //NSParameterAssert([curReach isKindOfClass: [Reachability class]]);
    if(curReach && [curReach isKindOfClass: [Reachability class]] && curReach == mWifiReach) {
    
		std::string newSSID = glf::GetSSID();
		if(newSSID != mLastSSID) {
			glf::PropertyMap& pm = glf::PropertyMap::Instance();
			int wifiChange = pm.GetPropertyInt32(gPropSessionWifiChange, 0);
			pm.SetPropertyT<int>(gPropSessionWifiChange, wifiChange + 1, glf::PropertyMap::EF_PERSISTANT|glf::PropertyMap::EF_LR_QUERY_SUSPENDRESUME|glf::PropertyMap::EF_LR_QUERY_FIRST);
			
			if(newSSID.empty() && !mLastSSID.empty())
				glf::Console::Println("[net] wifi lost (was %s)", mLastSSID.c_str());
			else if(mLastSSID.empty() && !newSSID.empty())
				glf::Console::Println("[net] connected to wifi %s", newSSID.c_str());
			else if(!mLastSSID.empty() && !newSSID.empty())
				glf::Console::Println("[net] wifi changed ssid from %s to %s", mLastSSID.c_str(), newSSID.c_str());
		}
		mLastSSID = newSSID;
		//NetworkStatus netStatus = [curReach currentReachabilityStatus];
		//BOOL connectionRequired= [curReach connectionRequired];
    }
    
}

- (void) createMainWindow
{
	UIScreen* screen = [UIScreen mainScreen];
	CGRect frame = screen.bounds;
	mMainWindow = [[UIWindow alloc] initWithFrame:frame];
	if (mIsMultipleScreenSupported)
	{
		mMainWindow.screen = screen;
	}
	mMainView = [self createViewForScreen:screen];
	[mMainWindow addSubview:mMainView];
	mMainWindow.hidden = NO;
	
	// Keyboard
	mTextField = [[UITextField alloc] initWithFrame: CGRectMake(0, 0, -10, -10)];
	mTextField.delegate = self;
	mTextField.keyboardType = UIKeyboardTypeDefault;
	[mMainView addSubview:mTextField];
	
	// Adding liviu's check for ipad stuff, since the flag is used for him almost exclusively anyways, might as well make 100% sure we get the same result he does. Yeah, I know it's a bit weak...
	size_t size;
	sysctlbyname("hw.machine", NULL, &size, NULL, 0);
	char *machine = (char*)malloc(size);
	sysctlbyname("hw.machine", machine, &size, NULL, 0);
	NSString *platform = [NSString stringWithCString:machine encoding: NSUTF8StringEncoding];
	free(machine);
	
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyPressed:) name:UITextFieldTextDidChangeNotification object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object: nil];
	
	mWifiReach = [[Reachability reachabilityForLocalWiFi] retain];
    [mWifiReach startNotifier];
	
	if([platform hasPrefix:@"iPad"])
	{
		// notification when the keyboard hides (for ipad only)	
        [[NSNotificationCenter defaultCenter] addObserver:self 
                                              selector:@selector(iPadKeyboardWillHide) 
                                              name:UIKeyboardWillHideNotification 
                                              object:nil];
	}

	m_activeTF = nil;
	m_skipKeyboardWillHideCallback = false;
	m_skipKeyPressCallback = false;
	mDefaultTextID = 0;
}

- (void) createSecondWindow:(UIScreen*)screen
{
	CGRect frame = screen.bounds;		
	mSecondWindow = [[UIWindow alloc] initWithFrame:frame];
	mSecondWindow.screen = screen;      
	mSecondView = [self createViewForScreen:screen];
	[mSecondWindow addSubview:mSecondView];
	mSecondWindow.hidden = NO;
	NSLog(@"Second window created: %fx%f\n", frame.size.width, frame.size.height);
}

- (GenericGLView *) createViewForScreen:(UIScreen *)screen
{
	GenericGLView *view = [self NewGLView:([UIScreen mainScreen] == screen)];	
	GLF_ASSERT(view);
	[view setCreationSettings:mCreationSettings];
	[view setScreen:screen];
	[view setContext:context];
	[view initWithFrame:[screen bounds]];
	return view;
}

- (int) GetFrameBufferAtScreenIndex:(int)screenIndex
{
	if (screenIndex == 0)
		return [mMainView viewFramebuffer];
	if (screenIndex == 1 && mSecondView)
		return [mSecondView viewFramebuffer];
	return -1;
}

- (void) GetWindowSizeAtScreenIndex:(int)screenIndex Width:(int &)width Height:(int &)height
{
	CGRect bounds = CGRectZero; 
	if (screenIndex == 0)
		bounds = mMainView.bounds;
	if (screenIndex == 1 && mSecondView)
		bounds = mSecondView.bounds;
	width = bounds.size.width;
	height = bounds.size.height;
}

- (void)handleScreenConnectNotification:(NSNotification*)aNotification
{
	NSLog(@"handleScreenConnectNotification\n");
	UIScreen* screen = [aNotification object];		
	[self addScreen:screen];
	
}

- (void)handleScreenDisconnectNotification:(NSNotification*)aNotification
{
	NSLog(@"handleScreenDisconnectNotification\n");
    UIScreen* screen = [aNotification object];
	[self removeScreen:screen];	
}

- (void)addScreen:(id)screenId
{
	UIScreen *screen = (UIScreen *)screenId;
	// Ensure previous window is removed
	[self removeScreen:screen];
	// Only second display is supported
	if ([UIScreen screens].count > 1 && [[UIScreen screens] objectAtIndex:1] == screen)
	{
		// Choose the smallest screen size as default
		glf::DisplayEvent dspConnecting(glf::DET_CONNECTING);
		dspConnecting.mCurrentResolution = 0;
		dspConnecting.mRank = 1;		
		dspConnecting.mResolutionCount = 0;
		for (int i = 0; i < (int)screen.availableModes.count; ++i)
		{
			UIScreenMode *mode = [screen.availableModes objectAtIndex:i];		
			NSLog(@"Screen mode #%d %fx%f\n", i, mode.size.width, mode.size.height);
			if ((mode.size.width * mode.size.height) < 
				(dspConnecting.mResolutions[dspConnecting.mCurrentResolution].x * 
				dspConnecting.mResolutions[dspConnecting.mCurrentResolution].y))
				dspConnecting.mCurrentResolution = i;
			dspConnecting.mResolutions[i].x = mode.size.width;
			dspConnecting.mResolutions[i].y = mode.size.height;
			dspConnecting.mResolutionCount++;
		}
		// Send connecting display event
		if (!GetAppEventMgr().SendEvent(dspConnecting) || 
			dspConnecting.mCurrentResolution >= dspConnecting.mResolutionCount)
		{
			NSLog(@"User abort usage of second display\n");
			return;
		}
		// Use potentially updated current mode
		screen.currentMode = [screen.availableModes objectAtIndex:dspConnecting.mCurrentResolution];
        if ([screen respondsToSelector:@selector(overscanCompensation)])
        {
            // Draw fullscreen framebuffer without black borders (since iOS 5.0)
			// Warning: this fix could clip a small part of the framebuffer
            screen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;
		}
        // Create second window
		[self createSecondWindow:screen];
		// Post connected display event after window creation
		glf::DisplayEvent dspConnected(glf::DET_CONNECTED);
		dspConnected.mRank = 1;
		dspConnected.mResolutions[0].x = dspConnecting.mResolutions[dspConnecting.mCurrentResolution].x;
		dspConnected.mResolutions[0].y = dspConnecting.mResolutions[dspConnecting.mCurrentResolution].y;
		GetAppEventMgr().PostEvent(dspConnected);
	}
}

- (void)removeScreen:(id)screenId
{
	UIScreen *screen = (UIScreen *)screenId;
	if (mSecondWindow && mSecondWindow.screen == screen)
	{
		// Send disconnecting display event
		glf::DisplayEvent dspDisconnecting(glf::DET_DISCONNECTING);
		dspDisconnecting.mRank = 1;		
		GetAppEventMgr().SendEvent(dspDisconnecting);
		// Destroy second window
		mSecondWindow.hidden = YES;
		[mSecondView release];
		mSecondView = nil;
		[mSecondWindow release];
		mSecondWindow = nil;
		NSLog(@"Second window destroyed\n");
		// Post disconnected display event after window destruction
		glf::DisplayEvent dspDisconnected(glf::DET_DISCONNECTED);
		dspDisconnected.mRank = 1;
		GetAppEventMgr().PostEvent(dspDisconnected);
	}
}

// Keyboard /////////////////////////////////////////////

- (void) UpdateKeyboard:(BOOL)visible DefaultText:(const std::wstring&)defaultText DefaultTextID:(int)defaultTextID
{
	if(visible && ((mTextField.userInteractionEnabled == NO) || (defaultTextID != mDefaultTextID)) )
	{
		m_skipKeyPressCallback = true;
		glf::Console::Println("Showing Keyboard...");
		mTextField.enablesReturnKeyAutomatically = NO;
		mTextField.text = [[NSString alloc] initWithCharacters:(const unichar*)defaultText.c_str() length:defaultText.size()];
		mTextField.clearsOnBeginEditing = NO;
		mTextField.autocorrectionType = UITextAutocorrectionTypeNo;
		mTextField.userInteractionEnabled = YES;
		mDefaultTextID = defaultTextID;
		[mTextField becomeFirstResponder];
		m_skipKeyPressCallback = false;
	}
	else if(!visible && (mTextField.userInteractionEnabled == YES))
	{
		glf::Console::Println("Hiding Keyboard...");
		mTextField.userInteractionEnabled = NO;
		[mTextField resignFirstResponder];
	}
}

 // executed when the keyboard hides
-(void) iPadKeyboardWillHide
{
    if (!m_skipKeyboardWillHideCallback && m_activeTF != nil)
    {
        m_activeTF = nil; // done editing
    }
}

struct Key {
	Key(wchar_t c, glf::Keyboard::KeyCode kc = glf::Keyboard::KEY_NOKEY)
		:	mChar(c)
		,	mKey(kc == glf::Keyboard::KEY_NOKEY ? gGameApp->GetInputMgr().GetKeyboard(0).GetKeyCode(c) : kc)
	{}
	wchar_t mChar;
	glf::Keyboard::KeyCode mKey;	
};

- (void)keyPressed:(NSNotification *)notification 
{
	if(m_skipKeyPressCallback)
		return;

	UITextField* tf = notification.object;
    
    // save currently active TF
    m_activeTF = tf;
	
	if(gGameApp && (tf == mTextField))
	{
		glf::KeyboardEvent ev(glf::IET_KEY_PRESSED);
		ev.mInputDevice = &gGameApp->GetInputMgr().GetKeyboard(0);
		
		std::vector<Key> events;
		
		int size = [tf.text length];
		
		for(int i = 0; i < size + 5; ++i) 
		{
			events.push_back(Key(0, glf::Keyboard::KEY_BACK));
		}
		
		for(int i = 0; i < size; ++i)
		{
			events.push_back(Key([tf.text characterAtIndex:i]));
		}
		
		for(size_t i = 0; i < events.size(); ++i) 
		{
			ev.mChar= events[i].mChar;
			ev.mKey	= events[i].mKey;
			
			GetAppEventMgr().PostEvent(ev);
		}
	}
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
	if(gGameApp && (textField == mTextField))
	{
		glf::KeyboardEvent ev(glf::IET_KEY_PRESSED);
		ev.mInputDevice = &gGameApp->GetInputMgr().GetKeyboard(0);
		ev.mChar = 0;
		ev.mKey = glf::Keyboard::KEY_RETURN;
		//ev->mModifiers is updated when event is dispatched via PreSendEvent
		GetAppEventMgr().PostEvent(ev);
		
		glf::Console::Println("return key");
	}
	
    return NO;
}

// End Keyboard /////////////////////////////////////////////

@end
